﻿/* This class parses a Vixen v. 2.0.5 file and saves the output to a binary file
 * for use by the Prop 128 light controller.  Vixen saves its output in an XML file.
 * Vixen stores the event values in Channel order, i.e. all of Channel 1 values, then 
 * all of Channel 2 values, etc.  The event values are encoded using Base64.  The Prop 128
 * controller needs these values stored in Event order, i.e. Event 1, Channels 1..n, then
 * Event 2, Channels 1..n, etc.  Additionally the Prop 128 controller needs the values in 
 * unencoded format.
 * 
 * The Prop 128 data is stored in the output binary file in the following order:
 * 1)  Channel Count (1 to 128)
 * 2)  Event Count High Byte
 * 3)  Event Count Low Byte (The total number of events in this sequence)
 * 4)  Event Period High Byte
 * 5)  Event Period Low Byte (The event period in milliseconds used by this sequence, usually <100)
 * 6)  Event Values in event order
 * 
 * Copyright 2009 Gregory M Bartlett
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Windows.Forms;
using System.Security.Permissions;

namespace Helix_Network_Supervisor_3
{
    class VixenXMLParser
    {
        private int time = 0; //Total Time in milliseconds of the sequence
        private int eventPeriod = 0; //Event Period in milliseconds
        private int channelCount = 0; //Number of Channels in the sequence
        private int eventCount; //Number of Events in the sequence
        private byte[,] eventValues; //Array of Event Values

        public int ReformatAndSaveVixenFile(string sourceFile, string destinationFile,
            int maxNumChannels, int channelOffset, int numChannels)
        {
            ParseVixenFile(sourceFile, maxNumChannels);
            saveOutputFileMode1(destinationFile, channelOffset, numChannels);
            return 1;
        }

        public int GetVixenVersionSelected(string fileName)
        {
            int versionNumber = 0;

            XmlTextReader reader = new XmlTextReader(fileName);

            while (reader.Read())
            {
                if (reader.Name == "Time")
                {
                    versionNumber = 2;
                    break;
                } // End if
                else if (reader.Name == "TimeInSeconds")
                {
                    versionNumber = 1;
                    break;
                } // End else if
            } // End while

            reader.Close();

            return versionNumber;
        } // End GetVixenVersionSelected()

        public int GetTimeFromVixenFile(string fileName)
        {
            int seqTime = 0;

            XmlTextReader reader = new XmlTextReader(fileName);

            while (reader.Read())
            {
                if (reader.Name == "Time")
                {
                    reader.Read();
                    seqTime = Int32.Parse(reader.Value);
                    seqTime = seqTime / 1000;
                    break;
                } // End if
                else if (reader.Name == "TimeInSeconds")
                {
                    reader.Read();
                    seqTime = Int32.Parse(reader.Value);
                    break;
                } // End else if
            } // End while

            reader.Close();

            return seqTime;
        } // End GetTimeFromVixenFile()

        public string GetSongTitleFromVixenFile(string fileName)
        {
            string songTitle = "";

            XmlTextReader reader = new XmlTextReader(fileName);

            while (reader.Read())
            {
                if ((reader.Name == "Audio") || (reader.Name == "Song"))
                {
                    songTitle = reader.GetAttribute(0);
                    break;
                } // End if
            } // End while
            reader.Close();

            return songTitle;
        } // End GetSongTitleFromVixenFile()

        public int GetNumberChannelsFromVixenFile(string fileName, string profilePath)
        {
            XmlTextReader reader = new XmlTextReader(fileName);

            channelCount = 0;

            while (reader.Read())
            {
                if ((reader.NodeType == XmlNodeType.Element) & (reader.Name == "Channels"))
                {
                    break;

                }
            }

            while (reader.Read())
            {
                if (reader.Name == "Channel" && reader.NodeType == XmlNodeType.Element)
                    channelCount++;

                if (reader.Name == "Channels")
                    break;
            }
            reader.Close();

            if (channelCount == 0) // Check to see if this sequence has a profile
            {
                reader = new XmlTextReader(fileName);

                while (reader.Read())
                {
                    if (reader.Name == "Profile")
                    {
                        reader.Read();
                        string profileFile = profilePath + @"\" + reader.Value + ".pro";
                        //MessageBox.Show("Profile Path " + profileFile);
                        //return 0;
                        channelCount = GetNumberChannelsFromProfile(profileFile);
                        break;
                    }
                }
                reader.Close();
            }

            return channelCount;
        }

        public int GetNumberChannelsFromProfile(string fileName)
        {
            try
            {
                XmlTextReader reader = new XmlTextReader(fileName);

                channelCount = 0;

                while (reader.Read())
                {
                    if ((reader.NodeType == XmlNodeType.Element) & (reader.Name == "ChannelObjects"))
                    {
                        break;

                    }
                }

                while (reader.Read())
                {
                    if (reader.Name == "Channel" && reader.NodeType == XmlNodeType.Element)
                        channelCount++;

                    if (reader.Name == "ChannelObjects")
                        break;
                }
                reader.Close();
                return channelCount;
            }
            catch
            {
                MessageBox.Show("Unable to open Profile Name:  " + Path.GetFileName(fileName), "Helix Show Builder - Error",
                   MessageBoxButtons.OK, MessageBoxIcon.Stop);

                return -1;
            }
        }

        public void ParseVixenFile(string fileName, int maxNumChannels)
        {
            //This method parses a Vixen v. 2.0.5 file to get the necessary data

            int channelLoop;
            int eventLoop;

            //Open an XML Text Reader using the file name provided by the OpenFileDialog
            XmlTextReader reader = new XmlTextReader(fileName);

            //Get the Time of the sequence in milliseconds
            while (reader.Read())
            {
                if (reader.Name == "Time")
                {
                    reader.Read();
                    time = Int32.Parse(reader.Value);
                    break;
                }
            }

            //Get the Event Period of the sequence in milliseconds
            while (reader.Read())
            {
                if (reader.Name == "EventPeriodInMilliseconds")
                {
                    reader.Read();
                    eventPeriod = Int32.Parse(reader.Value);
                    break;
                }
            }

            //Count the number of Channels in the sequence
            channelCount = 0;
            while (reader.Read())
            {
                if (reader.Name == "Channels")
                    break;
            }

            while (reader.Read())
            {
                if (reader.Name == "Channel" && reader.NodeType == XmlNodeType.Element)
                    channelCount++;

                if (reader.Name == "Channels")
                    break;
            }

            //Calculate the number of events in the sequence
            eventCount = time / eventPeriod;

            //Check for a partial event
            if ((eventCount * eventPeriod) < time)
            {
                //Add one for the partial event
                eventCount++;
            }

            //Initialize the Event Value array and set all elements to 0
            eventValues = new byte[maxNumChannels, eventCount];

            for (channelLoop = 0; channelLoop < maxNumChannels; channelLoop++)
            {
                for (eventLoop = 0; eventLoop < eventCount; eventLoop++)
                {
                    eventValues[channelLoop, eventLoop] = 0;
                }
            }

            //Get the Event Values.  Vixen stores the event values in Channel order, encoded in Base64.
            while (reader.Read())
            {
                if (reader.Name == "EventValues")
                    break;
            }

            byte[] tempEventValues = new byte[eventCount];

            for (channelLoop = 0; channelLoop < channelCount; channelLoop++)
            {
                reader.ReadBase64(tempEventValues, 0, eventCount);

                for (eventLoop = 0; eventLoop < eventCount; eventLoop++)
                {
                    eventValues[channelLoop, eventLoop] = tempEventValues[eventLoop];
                }
            }

            //Close the XML Text Reader
            reader.Close();
        }

        private void ParseVixenFile(string fileName, int maxNumChannels, string profilePath)
        {
            //This method parses a Vixen v. 2.0.5 file to get the necessary data

            int channelLoop;
            int eventLoop;

            //Count the number of Channels in the sequence
            channelCount = 0;
            channelCount = GetNumberChannelsFromVixenFile(fileName, profilePath);

            //Open an XML Text Reader using the file name provided by the OpenFileDialog
            XmlTextReader reader = new XmlTextReader(fileName);

            //Get the Time of the sequence in milliseconds
            while (reader.Read())
            {
                if (reader.Name == "Time")
                {
                    reader.Read();
                    time = Int32.Parse(reader.Value);
                    break;
                }
            }

            //Get the Event Period of the sequence in milliseconds
            while (reader.Read())
            {
                if (reader.Name == "EventPeriodInMilliseconds")
                {
                    reader.Read();
                    eventPeriod = Int32.Parse(reader.Value);
                    break;
                }
            }

            //Calculate the number of events in the sequence
            eventCount = time / eventPeriod;

            //Check for a partial event
            if ((eventCount * eventPeriod) < time)
            {
                //Add one for the partial event
                eventCount++;
            }

            //Initialize the Event Value array and set all elements to 0
            if (maxNumChannels >= channelCount)
            {
                eventValues = new byte[maxNumChannels, eventCount];

                for (channelLoop = 0; channelLoop < maxNumChannels; channelLoop++)
                {
                    for (eventLoop = 0; eventLoop < eventCount; eventLoop++)
                    {
                        eventValues[channelLoop, eventLoop] = 0;
                    }
                }
            }
            else
            {
                eventValues = new byte[channelCount, eventCount];

                for (channelLoop = 0; channelLoop < channelCount; channelLoop++)
                {
                    for (eventLoop = 0; eventLoop < eventCount; eventLoop++)
                    {
                        eventValues[channelLoop, eventLoop] = 0;
                    }
                }

            }

            //Get the Event Values.  Vixen stores the event values in Channel order, encoded in Base64.
            while (reader.Read())
            {
                if (reader.Name == "EventValues")
                    break;
            }

            byte[] tempEventValues = new byte[eventCount];

            for (channelLoop = 0; channelLoop < channelCount; channelLoop++)
            {
                reader.ReadBase64(tempEventValues, 0, eventCount);

                for (eventLoop = 0; eventLoop < eventCount; eventLoop++)
                {
                    eventValues[channelLoop, eventLoop] = tempEventValues[eventLoop];
                }
            }

            //Close the XML Text Reader
            reader.Close();
        }

        public void saveVixenFile(string sourcePath, string destinationPath, string destinationFileName,
            string profilePath, ListView.ListViewItemCollection ChannelMap)
        {
            // sourcePath is the path to the sequence to be saved.
            // destinationPath is the path to the correct directory in the upload directory
            // destinationFileName is the 8.3 formated file name of the sequence to be saved

            /* Search the Channel Map to determine the highest channel number
             *The Channel Map data is in a ListViewItemCollection with the following order:
             *Item[0]="Helix System XX"
             *Item[1]="Channel Bank 1...4"
             *Item[2]=Channel Bank Function
             *Item[3]=Start Channel
             *Item[4]=End Channel
             *Item[5]=System Number
            */

            int highestChannelNumber = 0;

            foreach (ListViewItem system in ChannelMap)
            {
                int testChannelNumber;
                bool success = int.TryParse(system.SubItems[4].Text, out testChannelNumber);
                if (!success)
                {
                    testChannelNumber = 0;
                }
                if (testChannelNumber > highestChannelNumber)
                {
                    highestChannelNumber = int.Parse(system.SubItems[4].Text);
                }
            }

            // Parse the Vixen file
            ParseVixenFile(sourcePath, highestChannelNumber, profilePath);

            int numHelixSystems = (ChannelMap.Count) / 4;

            byte eventCountH; //High byte of the event count
            byte eventCountL; //Low byte of the event count
            byte eventPeriodH; //High byte of the event period
            byte eventPeriodL; //Low byte of the event period

            //Break up the event count into bytes
            eventCountH = (byte)(eventCount / 256);
            eventCountL = (byte)(eventCount - (eventCountH * 256));

            //Break up the event period into bytes
            eventPeriodH = (byte)(eventPeriod / 256);
            eventPeriodL = (byte)(eventPeriod - (eventPeriodH * 256));

            for (int system = 0; system < numHelixSystems; system++)
            {
                int systemNumber = system + 1;
                string fileName = destinationPath + @"\Helix System " + systemNumber.ToString() + @"\" + destinationFileName;
                //Create a file stream using the file name provided by SaveFileDialog
                //If the file exists overwrite else create new
                FileStream stream = new FileStream(fileName, FileMode.Create);

                //Create a binary writer
                BinaryWriter bw = new BinaryWriter(stream);

                //bw.Write((byte)numChannels); //Save Channel Count
                bw.Write(eventCountH); //Save High Byte of Event Count 
                bw.Write(eventCountL); //Save Low Byte of Event Count
                bw.Write(eventPeriodH); //Save High Byte of Event Period
                bw.Write(eventPeriodL); //Save Low Byte of Event Period

                //Save the event values in event order for each channel bank
                for (int eventLoop = 0; eventLoop < eventCount; eventLoop++)
                {
                    for (int chBank = 0; chBank < 4; chBank++)
                    {
                        if (ChannelMap[system + chBank].SubItems[2].Text != "Unused")
                        {
                            int startChannel = int.Parse(ChannelMap[system + chBank].SubItems[3].Text) - 1;
                            int endChannel = int.Parse(ChannelMap[system + chBank].SubItems[4].Text);
                            for (int channelLoop = startChannel; channelLoop < endChannel; channelLoop++)
                            {
                                bw.Write(eventValues[channelLoop, eventLoop]);
                            }
                        }
                    } // End for chBank
                } // End for eventLoop

                bw.Close(); //Close binary writer
                stream.Close(); //Close file stream
            } // End for systems
        } // End saveVixenFile

        public void saveOutputFileMode1(string fileName, int channelOffset, int numChannels)
        {
            //This method saves the data to a binary file
            byte eventCountH; //High byte of the event count
            byte eventCountL; //Low byte of the event count
            byte eventPeriodH; //High byte of the event period
            byte eventPeriodL; //Low byte of the event period
            int channelLoop;
            int eventLoop;

            //Break up the event count into bytes
            eventCountH = (byte)(eventCount / 256);
            eventCountL = (byte)(eventCount - (eventCountH * 256));

            //Break up the event period into bytes
            eventPeriodH = (byte)(eventPeriod / 256);
            eventPeriodL = (byte)(eventPeriod - (eventPeriodH * 256));

            //Create a file stream using the file name provided by SaveFileDialog
            //If the file exists overwrite else create new
            FileStream stream = new FileStream(fileName, FileMode.Create);

            //Create a binary writer
            BinaryWriter bw = new BinaryWriter(stream);

            bw.Write((byte)numChannels); //Save Channel Count
            bw.Write(eventCountH); //Save High Byte of Event Count 
            bw.Write(eventCountL); //Save Low Byte of Event Count
            bw.Write(eventPeriodH); //Save High Byte of Event Period
            bw.Write(eventPeriodL); //Save Low Byte of Event Period

            //Save the event values in event order
            for (eventLoop = 0; eventLoop < eventCount; eventLoop++)
            {
                for (channelLoop = channelOffset; channelLoop < (channelOffset + numChannels); channelLoop++)
                {
                    bw.Write(eventValues[channelLoop, eventLoop]);
                }
            }

            bw.Close(); //Close binary writer
            stream.Close(); //Close file stream
        }

        public void saveOutputFileMode3(string fileName, int channelOffset, int numChannels, int maxNumChannels)
        {
            //This method saves the data to a binary file
            byte eventCountH; //High byte of the event count
            byte eventCountL; //Low byte of the event count
            byte eventPeriodH; //High byte of the event period
            byte eventPeriodL; //Low byte of the event period
            int channelLoop;
            int eventLoop;
            byte[] lastEvent = new byte[maxNumChannels];
            byte[] thisEvent = new byte[maxNumChannels];

            //Break up the event count into bytes
            eventCountH = (byte)(eventCount / 256);
            eventCountL = (byte)(eventCount - (eventCountH * 256));

            //Break up the event period into bytes
            eventPeriodH = (byte)(eventPeriod / 256);
            eventPeriodL = (byte)(eventPeriod - (eventPeriodH * 256));

            //Create a file stream using the file name provided by SaveFileDialog
            //If the file exists overwrite else create new
            FileStream stream = new FileStream(fileName, FileMode.Create);

            //Create a binary writer
            BinaryWriter bw = new BinaryWriter(stream);

            bw.Write((byte)numChannels); //Save Channel Count
            bw.Write(eventCountH); //Save High Byte of Event Count 
            bw.Write(eventCountL); //Save Low Byte of Event Count
            bw.Write(eventPeriodH); //Save High Byte of Event Period
            bw.Write(eventPeriodL); //Save Low Byte of Event Period

            for (int loop = 0; loop < maxNumChannels; loop++)
            {
                //lastEvent[loop] = eventValues[loop, 0];
                lastEvent[loop] = 0;
                //bw.Write(lastEvent[loop]);
            }

            for (channelLoop = channelOffset; channelLoop < (channelOffset + numChannels); channelLoop++)
            {
                bw.Write((byte)0);
            }

            bool writeEvent;

            //Save the event values in event order
            for (eventLoop = 0; eventLoop < eventCount; eventLoop++)
            {
                writeEvent = false;
                for (int loop = 0; loop < maxNumChannels; loop++)
                {
                    if (eventValues[loop, eventLoop] != lastEvent[loop])
                    {
                        writeEvent = true;
                        lastEvent[loop] = eventValues[loop, eventLoop];
                    }
                }

                if (writeEvent == true)
                {
                    for (channelLoop = channelOffset; channelLoop < (channelOffset + numChannels); channelLoop++)
                    {
                        bw.Write(eventValues[channelLoop, eventLoop]);
                    }
                }

            }

            byte zero = 0;

            for (int loop = 0; loop < numChannels; loop++)
            {
                bw.Write(zero);
            }

            bw.Close(); //Close binary writer
            stream.Close(); //Close file stream
        }
    }
}
